<?php
// +----------------------------------------------------------------------
// | title: 标题注释
// +----------------------------------------------------------------------
// | Author: TZ.Leong
// +----------------------------------------------------------------------
// | Date: 2019/7/8 18:51
// +----------------------------------------------------------------------
use App\Common\TCPHelper;
use cmf\lib\iplocation\IpLocation;
use think\facade\Log;

/**
 * 获取内部验证KEY
 * @param string $scene
 * @return mixed
 */


function getSecure(string $scene = "TASK")
{
    return config("secure." . GAME_NAME . "_key." . $scene);
}

/**
 * 获取ip地址所在地区
 * @param $ip
 * @return string
 */
function getIpFromAddress($ip): string
{
    static $_ipArr = array();
    if (empty($_ipArr[$ip])) {
        $ipLocation = new IpLocation();
        $ipInfo = $ipLocation->getlocation($ip);
        $address = $ipInfo["country"] . $ipInfo["area"];
        $_ipArr[$ip] = $address;
    }
    return $_ipArr[$ip];
}

/**
 * 根据PHP各种类型变量生成唯一标识号
 * @param mixed $mix 变量
 * @return string
 */
function to_guid_string($mix): string
{
    if (is_object($mix)) {
        return spl_object_hash($mix);
    }

    if (is_resource($mix)) {
        $mix = get_resource_type($mix) . (string) $mix;
    } else {
        $mix = serialize($mix);
    }
    return md5($mix);
}

/**
 * rpc远程服务调用函数
 * @param string $server 服务地址/统一服ID/查询条件
 * @param string $action 远程操作函数
 * @param string $params 远程函数参数
 * @return bool|object
 */
function rpc(string $server, string $action, $params)
{
    //客户端连接句柄池
    static $_handler = array();
    //判断句柄池中是否存储
    $key = to_guid_string($server);
    //没有连接过，处理链接
    if (!isset($_handler[$key])) {
        $client = new \hprose\HproseHttpClient($server);
        $client->setKeepAlive(true); //保持长连接
        $client->setKeepAliveTimeout(10000); //长连接超时时常(ms)
        $client->setTimeout(5000); //请求超时时间(ms)
        //加入句柄池
        $_handler[$key] = $client;
    }
    try {
        return $_handler[$key]->$action($params);
    } catch (\hprose\HproseException $e) {
        return false;
    }
}

/**
 * 创建rpc请求参数
 * @param array $params
 * @return array
 */
function createRpcParams($params = [])
{
    $params["time"] = time();
    $params["cipher"] = \cmf\lib\Sign::buildMD5sign($params, getSecure());
    return $params;
}

/**
 * 判断$checkIp 是否在 ip白名单 $ipArr 之中。
 * @param string $checkIp 需要判断的IP
 * @param array $ipArr /string $ipArr     可能包括这样格式的ip,ip范围,子网段：115.124.13.11 115.124.15.11-115.124.15.15 115.124.16.0/22
 * @return bool
 */
function checkIpRight(string $checkIp, array $ipArr): bool
{
    if ($checkIp == $ipArr) {
        return true;
    }

    if (in_array($checkIp, $ipArr, true)) {
        return true;
    }

    foreach ($ipArr as $cidr) {
        $ipLongArr = explode('-', $cidr);
        if (!empty($ipLongArr[1])) {
            if (ip2long($checkIp) >= ip2long(trim($ipLongArr[0])) && ip2long($checkIp) <= ip2long(trim($ipLongArr[1]))) {
                return true;
            }
        } else {
            $ipMaskArr = explode('/', $cidr);
            $mask = empty($ipMaskArr[1]) ? 32 : (int) $ipMaskArr[1];
            //这里的判断需要注意的是在32位系统下会有问题
            if ((ip2long($checkIp) >> (32 - $mask) == ip2long($ipMaskArr[0]) >> (32 - $mask))) {
                return true;
            }
        }
    }
    return false;
}

/**
 * 获取当前需要区分游戏，语言版本的配置项
 * @param string $file 文件名称
 * @param string $v 文件路径
 * @param string $path 路径 默认CMF_ROOT."data/config/"
 * @param string $ext 配置文件后缀，预防会有其他格式配置文件
 * @return mixed
 */
function getDiffConfig(string $file, $v = GAME_NAME, string $path = CMF_ROOT . "data/config/", $ext = ".php")
{
    static $_config = array();
    //    Log::write("debug log filename " . $path . $v . '/' . $file . $ext);
    $static = md5($path . $v . $file . $ext);
    if (!isset($_config[$static])) {
        $config = array();
        //是否有游戏配置
        $fileName = $path . $v . '/' . $file . $ext;
        if (is_file($fileName)) {
            $config = include($fileName);
        }
        $_config[$static] = $config;
    }
    return $_config[$static];
}






function getHeroConfig(int $heroId): array
{
    $heroConfig = getDiffConfig("hero_data");
    // Log::write("debug hero config".json_encode($heroConfig));

    foreach ($heroConfig as $k => $items) {
        if ($k == $heroId) {

            return $items;
        }
    }
    return ["name" => "", "quality" => "", "is_core" => "", "realm" => ""];
}


function getAssetsConfigByName(string $name): array
{
    $heroConfig = getDiffConfig("data_assets");
    // Log::write("debug hero config".json_encode($heroConfig));

    foreach ($heroConfig as $k => $value) {
        if ($value["name"] == $name) {
            return $value;
        }
    }
    return ["name" => $name];
}

function getItemConfigById(int $itemid): array
{
    $heroConfig = getDiffConfig("item");
    // Log::write("debug hero config".json_encode($heroConfig));

    foreach ($heroConfig as $k => $value) {
        if ($itemid == $k) {
            return ["name" => $value, "id" => $k, "desc" => $value];
        }

    }
    return ["id" => $k];
}

/**
 * 根据充值表class type 获取具体充值配置
 * @param $classType
 * @param $subtype
 * @param mixed|string $v
 * @param string $path
 * @param string $ext
 * @return array|mixed
 */
function getSubChargeConfig($classType, $subtype, $v = GAME_NAME, $path = CMF_ROOT . "data/config/", $ext = ".php")
{
    //    Log::write("debug log filename  path:" . $path . ", v:" . $v . '/' . ",file:" . $file . $ext);
    $file = "recharge_sub_type_" . $classType;
    static $_config = array();
    $static = md5($path . $v . $file . $ext);

    if (!isset($_config[$static])) {
        $config = array();
        //是否有游戏配置
        $fileName = $path . $v . '/' . $file . $ext;
        if (is_file($fileName)) {
            $config = include($fileName);
        }
        $_config[$static] = $config;
    }
    $chargeConfig = $_config[$static];
    return $chargeConfig[$subtype];
}

/**
 * 返回5分钟数组
 * @param int $sec 时间间隔，单位秒
 * @param bool $all 是否返回所有时间点
 * @return array
 */
function getTimeArr(int $sec = 300, bool $all = true)
{
    if ($all) {
        //一天所有时间
        $startTime = strtotime("00:05");
        $endTime = strtotime("24:00");
        $timeArr = array();
        while ($startTime <= $endTime) {
            $now = date("H:i", $startTime);
            $timeArr[] = $now === "00:00" ? "24:00" : $now;
            $startTime += $sec;
        }
    } else {
        //当前时间点
        $sec = $sec / 60;//多少分钟
        $hour = date("H");
        $minute = date("i");
        $timeArr = $hour . ":" . $minute[0] . ($minute[1] - ($minute[1] % $sec));
        $timeArr = ($timeArr == "00:00") ? "24:00" : $timeArr;
    }
    return $timeArr;
}

/**
 * 生成实时数据缓存
 * @param $map
 * @param $day
 * @param $validity
 * @return mixed
 */
function cacheCurrentTime($map, $day, $validity)
{
    $yesterday = date("Ymd", strtotime($day) - 86400);//昨日
    $weekDay = date("Ymd", strtotime($day) - 86400 * 7);//上周
    $lastMonth = date("Ymd", strtotime(getLastMonth($day)));//上月
    $twoLastMonth = date("Ymd", strtotime(getLastMonth($day, 2)));//上上月
    $redisKey = "current_time:" . md5(http_build_query($map) . session("__ROLE_PLATFORM__"));//redis key

    $modelClass = "\\app\\" . APP_NAME . "\\model\\DayOnlineModel";
    $onlineModel = new $modelClass();
    //今日在线数据
    $cacheData[1] = $onlineModel->getOnlineTxt($map);
    //    Log::write("debug last online sql" . $onlineModel->getLastSql() . "map" . json_encode($map));

    //昨天此时情况
    $map["stat_project"] = array("<>", 7);
    $map["day"] = $yesterday;
    $cacheData[2] = $onlineModel->getOnlineTxt($map);

    //上周此时情况
    $map["day"] = $weekDay;
    $cacheData[3] = $onlineModel->getOnlineTxt($map);

    //上个月此时情况
    $map["stat_project"] = 7;
    $map["day"] = $lastMonth;
    $lastMonthData = $onlineModel->getOnlineTxt($map);
    $cacheData[2][7] = $lastMonthData[7] ?? [];

    //上上个月此时情况
    $map["day"] = $twoLastMonth;
    $lastMonthData = $onlineModel->getOnlineTxt($map);
    $cacheData[3][7] = $lastMonthData[7] ?? [];
    //增加缓存
    if ($validity) {
        cache($redisKey, $cacheData, $validity);
    }
    return $cacheData;
}

/**
 * 实时数据统计
 * @param $map
 * @param $day
 * @param $validity
 * @return mixed
 */
function currentTime($map, $day, $validity = 60)
{
    $cacheData = [];
    if ($validity) {
        $redisKey = "current_time:" . md5(http_build_query($map) . session("__ROLE_PLATFORM__"));
        $cacheData = cache($redisKey);
    }
    if (empty($cacheData)) {
        $cacheData = cacheCurrentTime($map, $day, $validity);
        //        Log::write("debug cache " . json_encode($cacheData));
    }
    $data["data"] = $cacheData;
    //当前时间点
    $minute = getTimeArr(300, false);
    $lastKeyArr = array();
    foreach ($data["data"][1] as $project => $row) {
        //拿到最后一个数组
        $lastArr = array_slice($row, -1, 1);
        //拿到最后一个时间点
        $lastKey = key($lastArr);
        $lastKeyArr[$project] = $minute > $lastKey ? $lastKey : $minute;
        if (isset($row[$lastKeyArr[$project]])) {
            $data["now"][$project] = (int) $row[$lastKeyArr[$project]];
        }
    }
    //昨日此时数值
    foreach ($data["data"][2] as $project => $row) {
        if (isset($lastKeyArr[$project], $row[$lastKeyArr[$project]])) {
            $data["yesterday"][$project] = (int) $row[$lastKeyArr[$project]];
        }
    }
    //上周此时数值
    foreach ($data["data"][3] as $project => $row) {
        if (isset($lastKeyArr[$project], $row[$lastKeyArr[$project]])) {
            $data["week"][$project] = (int) $row[$lastKeyArr[$project]];
        }
    }
    return $data;
}

/**
 * 获取上N个月当前时间
 * @param $date
 * @param int $lastNum 上N个月
 * @return string
 */
function getLastMonth($date, int $lastNum = 1): string
{
    $nowTime = strtotime($date);
    $nowDay = date("d", $nowTime);
    $lastMonth = date("Y-m", strtotime("-{$lastNum} month", strtotime(date("Y-m", $nowTime))));
    $lastDay = date("t", strtotime($lastMonth));
    if ($nowDay <= $lastDay) {
        $lastDate = $lastMonth . "-" . $nowDay;
    } else {
        $lastDate = $lastMonth . "-" . $lastDay;
    }
    return $lastDate;
}

/**
 * 比较两个数值的升降
 * @param $min
 * @param $max
 * @return string
 */
function upOrdown($min, $max): string
{
    if ($min > $max) {
        $title = '<span style="color: green; "><b>↑</b></span>';
    } elseif ($min < $max) {
        $title = '<span style="color: red; "><b>↓</b></span>';
    } else {
        $title = '<span style="color: black; "><b>=</b></span>';
    }
    return $title;
}

/**
 * 合并2个或者以上个数组,如果键位相同，就尝试相加，
 * @param array $arr1 基数组
 * @param array $arr2 附加数组
 * [code]
 * $arr1 = array('9:05'=>array('a'=>1, 'b'=>2), '9:10'=>111);
 * $arr2 = array('9:05'=>array('a'=>1, 'b'=>2, 'c'=>222), '9:10'=>1, '9:15'=>99);
 * $arr3 = array('9:20'=>array('a'=>1, 'd'=>333));
 * arrayMergeSum($arr1, $arr2, $arr3);
 * [output]
 * Array ( [9:05] => Array ( [a] => 2 [b] => 4 [c] => 222 ) [9:10] => 112 [9:15] => 99 [9:20] => Array ( [a] => 1 [d] => 333 ) )
 * [/code]
 */
function arrayMergeSum(array $arr1, array $arr2)
{
    $args = func_get_args();
    $res = array_shift($args);
    while (!empty($args)) {
        $next = array_shift($args);
        foreach ($next as $k => $v) {
            //如果类型不一致
            if (isset($res[$k]) && gettype($res[$k]) !== gettype($v)) {
                return [];
            }

            if (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
                $res[$k] = arrayMergeSum($res[$k], $v);
            }
            //如果都是int类型，追加
//            else if (is_int($k)) {
//                if (isset($res[$k])) {
//                    $res[] = $v;
//                } else {
//                    $res[$k] = $v;
//                }
//            }
            //如果2个都是数组
            //其他情况
            else if (isset($res[$k])) {
                $res[$k] += $v;
            } else {
                $res[$k] = $v;
            }
        }
    }
    return $res;
}

/**
 * 启动计划任务
 * @param string $action 计划任务标识
 * @param string $params 传入参数
 * @param bool $needRes 是否需要执行结果
 * @param string $taskPath 计划任务入口
 * @return array|bool           true
 */
function runThread($action = "", $params = "", $needRes = false, $taskPath = "")
{
    if (empty($action)) {
        return false;
    }
    //计划任务入口:Content/SingleTask/SysMail bzsc Single
    $taskPath = empty($taskPath) ? $action . " " . GAME_NAME . " " . APP_NAME : $taskPath;
    //php命令
    $php = strpos(PHP_OS, 'WIN') !== false ? "php" : "/usr/local/php/bin/php";
    //定义计划任务路径
    $taskPath = CMF_ROOT . " && {$php} think " . $taskPath . " " . $params;
    //启动计划任务
    if ($needRes) {
        @exec("cd " . $taskPath . " &", $result);
        return $result;
    }

    $taskOpen = popen("cd " . $taskPath . " &", "w");
    pclose($taskOpen);
    return true;
}

/**
 * 检测计划任务是否正在执行
 * @param string $action 计划任务标识
 * @param int $checkNum 检测计划任务数量
 * @return bool             true--是；false--否
 */
function isThreadRunning($action, $checkNum = 0): bool
{
    @exec('ps -ef | egrep "think ' . $action . ' ' . GAME_NAME . '" | grep -v "grep"', $result);
    return count($result) > $checkNum;
}

/**
 * 生成验证码
 * @param array $data
 * @param string $secretKey
 * @return boolean|string
 */
function createSign(array $data, string $secretKey)
{
    if (empty($data)) {
        return false;
    }
    if (empty($secretKey)) {
        return false;
    }
    unset($data['sign']);
    ksort($data);
    //把所有的值级成字符串
    $string = implode('', $data);
    return md5(md5($string) . $secretKey);
}

/**
 * 字符截取
 * @param $sourcestr
 * @param $length
 * @param string $dot
 * @return string
 */
function strCut($sourcestr, $length, $dot = '...')
{
    $restructure = '';
    $i = 0;
    $n = 0;
    $str_length = strlen($sourcestr); //字符串的字节数
    while (($n < $length) && ($i <= $str_length)) {
        $temp_str = substr($sourcestr, $i, 1);
        $ascnum = Ord($temp_str); //得到字符串中第$i位字符的ascii码
        if ($ascnum >= 224) {//如果ASCII位高与224，
            $restructure .= substr($sourcestr, $i, 3); //根据UTF-8编码规范，将3个连续的字符计为单个字符
            $i += 3; //实际Byte计为3
            $n++; //字串长度计1
        } elseif ($ascnum >= 192) { //如果ASCII位高与192，
            $restructure .= substr($sourcestr, $i, 2); //根据UTF-8编码规范，将2个连续的字符计为单个字符
            $i += 2; //实际Byte计为2
            $n++; //字串长度计1
        } elseif ($ascnum >= 65 && $ascnum <= 90) { //如果是大写字母，
            $restructure .= substr($sourcestr, $i, 1);
            ++$i; //实际的Byte数仍计1个
            $n++; //但考虑整体美观，大写字母计成一个高位字符
        } else {//其他情况下，包括小写字母和半角标点符号，
            $restructure .= substr($sourcestr, $i, 1);
            ++$i;            //实际的Byte数计1个
            $n += 0.5;        //小写字母和半角标点等与半个高位字符宽...
        }
    }
    if ($str_length > strlen($restructure)) {
        $restructure .= $dot; //超过长度时在尾处加上省略号
    }
    return $restructure;
}

/**
 * 显示柱状图
 * @param array $legendArr 标题数组
 * @param array $xAxisArr 横坐标数组
 * @param array $dataInfo 横坐标对应的值的数组
 * @param bool $loop 是循环拿值还是直接在外部组装好传入
 * @param string $formatter 纵坐标单位符号，如：%
 * @param int $interval 横坐标间隔数
 * @return string json
 */
function ECharts($legendArr, $xAxisArr, $dataInfo, $loop = true, $formatter = "", $interval = 0)
{
    //有多个值，则要循环取值
    if ($loop) {
        $seriesArr = [];
        foreach ($dataInfo as $name => $row) {
            //一般按顺序，先排序
            ksort($row);
            $seriesArr[] = array("name" => $name, "type" => "bar", "data" => array_values($row));
        }
    } else {
        $seriesArr = $dataInfo;
    }
    //组装柱状图数组
    $option = array(
        "tooltip" => array("trigger" => "axis", "axisPointer" => array("type" => "shadow")),
        "legend" => array("data" => $legendArr),//柱形图标题显示
        "grid" => array("left" => "65", "right" => "30"),//左右位置
        "xAxis" => array("type" => "category", "axisLabel" => array("interval" => $interval), "data" => $xAxisArr),//横坐标
        "yAxis" => array("type" => "value", "axisLabel" => array("formatter" => "{value}{$formatter}")),//纵坐标
        "series" => $seriesArr,
    );
    return json_encode($option);
}

/**
 * 数据导出到excel
 * @param $data
 * @param string $fileName
 * @return void
 * @throws PHPExcel_Exception
 * @throws PHPExcel_Reader_Exception
 * @throws PHPExcel_Writer_Exception
 */
function exportExcel($data, string $fileName = "export")
{
    $phpExcel = new \PHPExcel();
    $objActSheet = $phpExcel->getActiveSheet();
    //$i 为excel 行号
    foreach ($data as $i => $rows) {
        //行写入
        $line = ord("A");
        foreach ($rows as $keyName => $value) {
            // 列写入
            $column = chr($line);
            $objActSheet->setCellValue($column . ($i + 1), ' ' . $value);
            $line++;
        }
    }
    $fileName = iconv("utf-8", "gb2312//TRANSLIT//IGNORE", $fileName);
    // 设置活动单指数到第一个表,所以Excel打开这是第一个表
    $phpExcel->setActiveSheetIndex(0);
    header('Content-Type: application/vnd.ms-excel');
    header("Content-Disposition: attachment;filename=" . $fileName . ".xls");
    header('Cache-Control: max-age=0');
    $objWriter = \PHPExcel_IOFactory::createWriter($phpExcel, 'Excel5');
    $objWriter->save('php://output'); // 文件通过浏览器下载
    return;
}

/**
 * 中文转ascii码<<228,184,173,229,155,189>>
 * @param string $string 中文字符串
 * @param bool $add 是否两边加双尖括号
 * @return string
 */
function string2ascii(string $string = "", bool $add = true): string
{
    //截取中文
    $arr = str_split($string);
    $ascii = "";
    //循环字符转换成ascii码数值
    foreach ($arr as $v) {
        $ascii .= ord($v) . ",";
    }
    //去掉最后多出的英文逗号
    $ascii = rtrim($ascii, ",");
    //是否ascii两边加上双尖括号
    return $add ? "<<" . $ascii . ">>" : $ascii;
}

/** 返回文件从X行到Y行的内容(支持php5、php4)
 * @param string $filename 文件名
 * @param int $startLine 开始的行数
 * @param int $endLine 结束的行数
 * @param string $method
 * @return array
 */
function getFileLines(string $filename, int $startLine = 1, int $endLine = 50, string $method = 'rb'): array
{
    $content = array();
    $count = $endLine - $startLine;
    $fp = new SplFileObject($filename, $method);
    $fp->seek($startLine - 1);// 转到第N行, seek方法参数从0开始计数
    for ($i = 0; $i <= $count; ++$i) {
        $content[] = $fp->current();// current()获取当前行内容
        $fp->next();// 下一行
    }
    return array_filter($content); // array_filter过滤：false,null,''
}




/**
 * @param int $params
 * @return string
 */
function intToBin(int $params): string
{
    return decbin($params);
}


/**
 * 字符串转二进制
 * @param $str
 * @return false|string
 */
function stringToBinary($str)
{
    $hex = bin2hex($str);
    return hex2bin($hex);
}


